iT邦幫忙

2022 iThome 鐵人賽

DAY 5
1

Day5 蓋一棟 Django 小屋

想要蓋一棟房子,可不是只需要雙手就能辦到,還需要許多工具來一起完成。我們可以將會使用到的套件在建立 image 時一同裝好,再將不同的服務進行整合,解放雙手來一鍵啟動。

本篇除了要把相關套件安裝至容器,還會帶大家一步一步地從 Dockerfile 進化至 Docker compose,把 Django 與其他服務交給多容器管理工具來打理。

requirements.txt 套件清單

requirements.txt 是 Python 使用 pip 安裝時會需要的管理工具,現在會先將之後使用到的套件一併安裝(有些目前還不會使用到)。

建立一個名為 requirements.txt 的文件

cd ~/exmaple_tenant
touch requirements.txt

寫入以下內容,這些是接下來將會使用到的 Python 套件

django-tenants==3.4.3
django==3.2.5
django-tenants-q==1.0.0
django-elasticsearch-dsl==7.1.1
django_q==1.3.9
django_redis==5.2.0
psycopg2==2.9.3
uwsgi==2.0.20

Django Dockerfile

接續上一篇文章『建造地基!使用 Docker』的 Dockerfile,我們要把會使用到的相關套件安裝至容器,除了有上方的 python 套件,還有 PostgreSQL 的相依套件。

更新 Dockerfile

FROM ubuntu:22.04

# set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV DEBIAN_FRONTEND=noninteractive TZ="Asia/Taipei"
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8

# Install python 3.10
RUN apt-get update -y
RUN apt-get install gcc python3.10 python3.10-dev -y

# # Install PostgresSQL
RUN apt-get update && apt-get -y install postgresql libpq-dev postgresql-client postgresql-client-common 
RUN apt-get update && apt-get -y install python3-psycopg2 gettext

# Install pip
RUN apt-get install curl -y
RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
RUN python3.10 get-pip.py

# set work directory
RUN mkdir -p /opt/app
COPY . /opt/app
RUN pip install -r /opt/app/requirements.txt

建立 Image

docker build -t example_tenant . # 使用當前目錄的 Dockerfile 建立容器

使用 run 運行 container,這裡使用了許多參數,

-u 使用當前非 root 使用者(提示找不到名稱是正常的)

-t 分配一個虛擬終端

-i 讓容器的標準輸入保持打開

-p 進行 port 對映,可從本地主機的 port 連到容器中的 port

-v 將資料進行映射,本地主機的指定目錄對映 container 中的指定目錄(本地主機的為主)

docker run -it -u $(id -u):$(id -g)   \
       --name example_tenant          \
       -p 8000:8000                   \
       -v ~/example_tenant:/opt/app   \
       example_tenant /bin/bash 

建立 Django 專案

cd /opt/app/  # 切換到我們複製 requirements.txt 的目錄
django-admin startproject main # 建立 Django Project

ls -al # 查看當前目錄
./
../
Dockerfile
main/ # 新建的 Django Project
requirements.txt

測試運行

mv main web # 調整目錄名稱

cd web
python3.10 manage.py migrate
python3.10 manage.py runserver 0.0.0.0:8000

...
System check identified no issues (0 silenced).
September 03, 2022 - 10:13:43
Django version 3.2.5, using settings 'example_tenant.settings'
Starting development server at http://0.0.0.0:8000/
Quit the server with CONTROL-C.

運行成功!
https://ithelp.ithome.com.tw/upload/images/20220917/20151656tY8gBSxaIU.png

使用 docker-entrypoint 自動運行 Django runserver

專案建立後,每次啟動服務後都要手動執行 runserver 是很繁瑣的,讓我們來變一下魔法吧!

本地主機建立 docker-entrypoint.sh,讓容器啟動的時候自動執行指令

cd ~/example_tenant
touch docker-entrypoint.sh
chmod 755 docker-entrypoint.sh # 調整權限

寫入以下內容

#!/bin/bash
cd /opt/app/web/

# Apply database migrations
echo "Apply database migrations"
python3.10 manage.py migrate

# Start server
echo "Starting server"
python3.10 manage.py runserver 0.0.0.0:8000
exec "$@"

加入 Dockerfile

FROM ubuntu:22.04

...

RUN chmod +x /opt/app/docker-entrypoint.sh
ENTRYPOINT [ "/opt/app/docker-entrypoint.sh" ]

重新 build

docker build -t example_tenant . # 使用當前目錄的 Dockerfile 建立容器

運行容器

使用 -d 讓容器在背景運行

docker stop  example_container  # 停止原本的容器
docker rm  example_container    # 刪除原本的容器

docker run -itd --name example_container \
       -p 8000:8000                     \
       -v ~/example_tenant:/opt/app     \
       example_tenant /bin/bash 

entrypoint 完成了,我們接著進入下一步

使用 Docker-compose

用 docker run 的方式可以達到我們的目的,但是一次只能啟動一個容器,且都要打許多參數。使用 Docker-compose 可以更輕鬆更乾淨的幫助我們運行服務。

建立 docker-compose.yml

cd ~/example_tenant
touch docker-compose.yml

寫入以下內容

version: "3.9"

services:
  web:
    image: example_tenant:latest           # image 名稱
    build: .                               # 當 --build 執行時的 build 執行目錄
    container_name: example_container_web  # 容器名稱
    restart: always                        # 自動重啟
    user: "1000"                           # 指定非 root 使用者
    ports:
        - "8000:8000"                      # port 映射
    volumes:
      - .:/opt/app                         # 資料卷對映(. 為當前目錄)

運行 docker-compose

docker stop  example_container  # 停止原本的容器
docker rm  example_container    # 刪除原本的容器

docker compose up -d # 背景執行

...

[+] Running 2/2
 ⠿ Network example_tenant_default   Created                                                   0.1s
 ⠿ Container example_container_web  Started

運行成功!

https://ithelp.ithome.com.tw/upload/images/20220917/20151656j2DsshsitQ.png

加入 PostgreSQL 服務

若要使用多 schema 架構,使用 Django 自帶的 SQLite 是不足夠的,現在我們來安裝 PostgreSQL。

將原先的 docker-compose.yml 修改為以下內容,加入 db 服務

version: "3.9"

services:
  db:
    image: postgres
    container_name: example_tenant_db
    restart: always
    environment:
      - POSTGRES_USER=db_user
      - POSTGRES_PASSWORD=db_password
      - POSTGRES_DB=db_name
    healthcheck:
        test: [ "CMD", "pg_isready", "-q", "-d", "db_name", "-U", "db_user" ]
        timeout: 20s
        interval: 10s
        retries: 10
    volumes:
      - ./data/postgresql/data:/var/lib/postgresql/data
  web:
    image: example_tenant:latest
    build: .
    container_name: example_container_web
    restart: always
    user: "1000"
    ports:
        - "8000:8000"
    volumes:
      - .:/opt/app
    depends_on:
      db:
        condition: service_healthy
    links:
      - db

運行 docker- compose

docker-compose down -v
docker-compose up -d

...

[+] Running 3/3
 ⠿ Network example_tenant_default   Created
 ⠿ Container example_container_db   Healthy
 ⠿ Container example_container_web  Started

運行成功!

在 Compose 中使用 .env file 環境變量

當 compose 文件中有敏感資訊或是會經常調整的參數時,使用 .env 可以幫助我們很好的解決問題,讓我們來看看要怎麼進行參數化吧!

準備 .env file

cd ~/example_tenant
touch .env

在 .env 寫入參數

# Docker Compose .env
IMAGE_NAME=example_tenant
POSTGRES_DB=db_name
POSTGRES_USER=db_user
POSTGRES_PASSWORD=db_password
WEB_PORT=8000
UID=1000

調整 docker-compose.yml

version: "3.9"

services:
  db:
    image: postgres
    container_name: ${IMAGE_NAME}_db
    restart: always
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
    healthcheck:
        test: [ "CMD", "pg_isready", "-q", "-d", "${POSTGRES_DB}", "-U", "${POSTGRES_USER}" ]
        timeout: 20s
        interval: 10s
        retries: 10
    volumes:
      - ./data/postgresql/data:/var/lib/postgresql/data
  web:
    image: example_tenant:latest
    build: .
    container_name: ${IMAGE_NAME}_web
    restart: always
    user: "${UID}"
    ports:
        - "${WEB_PORT}:8000"
    volumes:
      - .:/opt/app
    depends_on:
      db:
        condition: service_healthy
    links:
      - db

重新運行

docker-compose down -v
docker-compose up -d

...

[+] Running 3/3
 ⠿ Network example_tenant_default   Created
 ⠿ Container example_container_db   Healthy
 ⠿ Container example_container_web  Started                                                                             11.7s

完成!

一棟 Django 魔法小屋就這樣完成了,今天詳細講解了該如何使用 Docker 打造容器化的環境,接下來我們就要開始『設計格局,Django 多租戶架構』


上一篇
Day4 建造地基!使用 Docker
下一篇
Day6 設計格局,Django 多租戶架構
系列文
全能住宅改造王,Django 多租戶架構的應用 —— 實作一個電商網站30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
terrytsang0618
iT邦新手 5 級 ‧ 2022-11-17 16:01:37

你好,我是剛剛在學,覺得你的教學很有用
但是我有一點問題
我docker run完後去/opt/app run django-admin startproject main
結果顯示: CommandError: [Errno 13] Permission denied: '/opt/app/main'
在docker我應該不會用sudo吧

看更多先前的回應...收起先前的回應...

很高興這篇文章有幫助到你~

docker run -it -u $(id -u):$(id -g)   \
       --name example_tenant          \
       -p 8000:8000                   \
       -v ~/example_tenant:/opt/app   \
       example_tenant /bin/bash 

docker run 進入容器環境後我們切換到容器中的 /opt/app 目錄,這個容器中的目錄是對應到本地主機的 ~/example_tenant 目錄。

如果出現 Permission denied 問題可以檢查在本地主機的目錄權限是不是與執行 docker run 的使用者不同。再請你試試看吧!

Local env:
terry:docker
Docker env:
root:nogroup
Docker ENV的是這樣是正常的嗎?
請問我應該是換local的嗎?
#cat /etc/group:
docker:x:1001:terry
#id -u:
1000
#id -g:
1001

主要是看 example_tenant 這個目錄在本地主機的權限設定
本地主機權限對應的的 uid gid 會是容器中的目錄權限,

以我自己的環境為範例,本地主機的 uid 與 gid 與容器中的對應

$ id -u
1000
$ id -g
1000

Local ENV:

cd ~
ls -al
drwxrwxr-x  3 ivankao ivankao   4096 Nov 18 08:20 example_tenant/

Docker ENV

cd /opt
ls -al
drwxrwxr-x 3 1000  1000 4096 Nov 18 16:20 app

如果你 example_tenant 的目錄權限是 terry docker 會對應到 1000 1001
你可以這樣下指令

docker run -it -u 1000:1001   \
       --name example_tenant          \
       -p 8000:8000                   \
       -v ~/example_tenant:/opt/app   \
       example_tenant /bin/bash 

我CHECK了example_tenant的dir對應的是1000:1001
Terminal用的也是1000:1001
DOCKER RUN的時候也是用1000:1001
但是Permission Denied仍然存在

哈囉,需要的話我可以遠端幫你一起看看問題,我也想知道是什麼原因造成的,要的話請站內簡訊給我聯繫方式~

我要留言

立即登入留言